
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** @author  John Miller
 *  @version 1.1
 *  @date    Wed Dec 30 14:48:41 EST 2009
 *  @see     LICENSE (MIT style license file).
 */

package apps.event

import collection.mutable.Queue

import scalation.model.TimeUnits._
import scalation.event.{Entity, Event, Model}
import scalation.linalgebra.{MatrixD, VectorD}
import scalation.queueingnet.JacksonNet
import scalation.random.{Exponential, Variate}
import scalation.stat.Statistic

/**:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 * The `Bank` object defines a particular scenario under which to execute the Bank
 * model.
 * @see scalation.event.ModelTest for another example of test code.
 */
object Bank extends App
{
    val stream     = 1                                       // random number stream to use (0 to 99)
    val lambda     = 6.0                                     // customer arrival rate (per hour)
    val mu         = 7.5                                     // customer service rate (per hour)
    val maxCusts   = 10                                      // stopping rule: simulate maxCusts customers
    val iArrivalRV = Exponential (HOUR/lambda, stream)       // inter-arrival time random variate
    val serviceRV  = Exponential (HOUR/mu, stream)           // service time random variate
    new BankModel ("Bank", maxCusts, iArrivalRV, serviceRV)

} // Bank


//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** The `BankModel` class defines a simple event-scheduling model of a Bank where
 *  service is provided by one teller and models an M/M/1 queue.
 *  @param name        the name of the simulation model
 *  @param nArrivals   the number of arrivals to generate (stopping condition)
 *  @param iArrivalRV  the inter-arrival time distribution
 *  @param serviceRV   the service time distribution
 */
class BankModel (name: String, nArrivals: Int, iArrivalRV: Variate, serviceRV: Variate)
      extends Model (name, false)
{
    val waitQueue = new Queue [Entity]

    //:: define the state variables for the simulation

    var nArr = 0.0                           // number of customers that have arrived
    var nIn  = 0.0                           // number of customers in the bank
    var nOut = 0.0                           // number of customers that have finished and left the bank

    val serviceTStat = new Statistic ()      // service time statistics
    val systemTStat  = new Statistic ()      // system time statistics

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Create a subclass of `Event` for Arrival events.
     *  @param customer  the entity that arrives, in this case a customer
     */
    case class Arrival (customer: Entity) extends Event (customer, this)      // entity, model
    {
        override def occur ()
        {
            super.occur ()
            if (nArr < nArrivals-1) {
                val iArrivalT = iArrivalRV.gen
                val next2Arrive = Entity (clock + iArrivalT, serviceRV.gen)
                schedule (iArrivalT, Arrival (next2Arrive))
            } // if
            if (nIn > 0) waitQueue.enqueue (customer)
            else         serviceTStat.tally (schedule (customer.serviceT, Departure (customer)))
            nArr += 1                                                   // update the current state
            nIn  += 1
        } // occur

    } // Arrival class

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Create a subclass of `Event` for Departure events.
     *  @param customer  the entity that departs, in this case a customer
     */
    case class Departure (customer: Entity) extends Event (customer, this)    // entity, model
    {
        override def occur ()
        {
            super.occur ()
            systemTStat.tally (clock - customer.arrivalT)
            if (nIn > 1) {
                val next4Service = waitQueue.dequeue ()                 // first customer in queue
                serviceTStat.tally (schedule (next4Service.serviceT, Departure (next4Service)))
            } // if
            nIn  -= 1                                                   // update the current state
            nOut += 1
        } // occur

    } // Departure class

    //:: start the simulation after scheduling the first priming event

    schedule (0.0, Arrival (Entity (0.0, serviceRV.gen)))
    simulate (0.0)                                                          // start simulating
    report (Array (("nArr", nArr), ("nIn", nIn), ("nOut", nOut)))
    reports (Array (("serviceTStat", serviceTStat), ("systemTStat", systemTStat)))

    //:: verify the results using a Jackson Queueing Network consiting of one node

    println ("\nVerification ...")
    val mm1 = new JacksonNet (new MatrixD ((1, 1), 0.0), VectorD (6.0/HOUR), VectorD (7.5/HOUR))
    mm1.check
    mm1.report

} // BankModel class

